<?php

declare(strict_types=1);

namespace app\service\sys;

use app\model\SysConfig;
use InvalidArgumentException;
use think\facade\Cache;

class ConfigService
{
    private const ALLOWED_TYPES = ['string', 'json', 'bool', 'int', 'decimal', 'float'];

    private $configCache = [];
    private $cacheTtl = 3600; // 缓存时间(秒)

    public function get(string $key, $default = null)
    {
        if (array_key_exists($key, $this->configCache)) {
            return $this->configCache[$key];
        }

        $cacheKey = 'sys_config_' . $key;
        if (Cache::has($cacheKey)) {
            $value = Cache::get($cacheKey);
        } else {
            /** @var SysConfig|null $config */
            $config = SysConfig::where('config_key', $key)->find();
            if (!$config) {
                return $default;
            }
            $value = $config->getValue();
            Cache::set($cacheKey, $value, $this->cacheTtl);
        }

        $this->configCache[$key] = $value;
        return $value;
    }

    public function set(string $key, $value, ?string $type = null): bool
    {
        $valueType = $this->resolveValueType($type, $value);
        $payload = [
            'config_key' => $key,
            'config_value' => ConfigTransformer::serializeValue($value, $valueType),
            'value_type' => $valueType,
        ];

        $exists = SysConfig::where('config_key', $key)->find();
        $result = $exists
            ? $exists->save($payload)
            : SysConfig::create($payload);

        if ($result) {
            $this->invalidate($key);
            $this->configCache[$key] = is_array($value) ? $value : $payload['config_value'];
        }

        return (bool) $result;
    }

    public function create(array $payload): SysConfig
    {
        $configKey = (string) ($payload['config_key'] ?? '');
        if ($configKey === '') {
            throw new InvalidArgumentException('配置键不能为空');
        }

        $exists = SysConfig::where('config_key', $configKey)->find();
        if ($exists) {
            throw new InvalidArgumentException('配置键已存在');
        }

        $data = $this->preparePayload($payload, true);

        /** @var SysConfig $config */
        $config = SysConfig::create($data);
        $this->invalidate($configKey);

        return SysConfig::find((int) $config->id);
    }

    public function update(SysConfig $config, array $payload): SysConfig
    {
        $data = $this->preparePayload($payload, false, $config);
        if ($data === []) {
            return $config;
        }

        $config->save($data);
        $this->invalidate((string) $config->config_key);

        return SysConfig::find((int) $config->id);
    }

    public function delete(SysConfig $config): bool
    {
        $key = (string) $config->config_key;
        $result = (bool) $config->delete();
        if ($result) {
            $this->invalidate($key);
        }

        return $result;
    }

    public function format(SysConfig $config): array
    {
        $data = $config->toArray();
        $data['config_value'] = ConfigTransformer::castValue(
            $data['config_value'] ?? null,
            (string) ($data['value_type'] ?? 'string')
        );

        return $data;
    }

    public function invalidate(string $key): void
    {
        $cacheKey = 'sys_config_' . $key;
        Cache::delete($cacheKey);
        unset($this->configCache[$key]);
    }

    /**
     * 清除所有配置缓存
     */
    public function clearCache(): void
    {
        // 清除所有相关缓存
        $keys = array_keys($this->configCache);
        foreach ($keys as $key) {
            $cacheKey = 'sys_config_' . $key;
            Cache::delete($cacheKey);
        }

        // 清空本地缓存数组
        $this->configCache = [];
    }

    private function resolveValueType(?string $type, mixed $value): string
    {
        if ($type !== null && in_array($type, self::ALLOWED_TYPES, true)) {
            return $type === 'float' ? 'float' : $type;
        }

        if (is_array($value) || is_object($value)) {
            return 'json';
        }

        if (is_bool($value)) {
            return 'bool';
        }

        if (is_int($value)) {
            return 'int';
        }

        if (is_float($value)) {
            return 'decimal';
        }

        if (is_numeric($value)) {
            return str_contains((string) $value, '.') ? 'decimal' : 'int';
        }

        return 'string';
    }

    private function preparePayload(array $payload, bool $isCreate, ?SysConfig $config = null): array
    {
        $data = [];

        if ($isCreate) {
            $data['config_key'] = (string) ($payload['config_key'] ?? '');
        }

        $valueProvided = array_key_exists('config_value', $payload);
        $typeProvided = array_key_exists('value_type', $payload);

        $valueType = $typeProvided
            ? (string) $payload['value_type']
            : ($isCreate
                ? $this->resolveValueType(null, $payload['config_value'] ?? null)
                : (string) ($config?->value_type ?? 'string'));

        if (!in_array($valueType, self::ALLOWED_TYPES, true)) {
            throw new InvalidArgumentException('配置值类型不正确');
        }

        if ($valueProvided || $isCreate || $typeProvided) {
            if ($valueProvided) {
                $value = $payload['config_value'];
            } elseif ($config) {
                $value = ConfigTransformer::castValue(
                    $config->getAttr('config_value'),
                    (string) $config->value_type
                );
            } else {
                $value = null;
            }

            $data['config_value'] = ConfigTransformer::serializeValue($value, $valueType);
        }

        $data['value_type'] = $valueType;

        if ($isCreate) {
            $data['description'] = (string) ($payload['description'] ?? '');
            $data['category'] = (string) ($payload['category'] ?? 'general');
            $data['status'] = isset($payload['status']) ? (int) $payload['status'] : 1;
        } else {
            if (array_key_exists('description', $payload)) {
                $data['description'] = (string) $payload['description'];
            }
            if (array_key_exists('category', $payload)) {
                $data['category'] = (string) $payload['category'];
            }
            if (array_key_exists('status', $payload)) {
                $data['status'] = (int) $payload['status'];
            }
        }

        return $data;
    }
}
